﻿using System;
using System.Linq.Expressions;

namespace TestByWire
{
    public class A : Recorder
    {
        public static Invocation<T, TResult> CallTo<T, TResult>(Expression<Func<T, TResult>> func) where T : class
        {
            return CallTo(null, func);
        }

        public static Invocation<T, TResult> CallTo<T, TResult>(String name, Expression<Func<T, TResult>> func) where T : class
        {
            if (!Scenario.IsRecording())
                throw new InvalidContextException("A statements can only be used in an external scenario");

            Invocation<T, TResult> invocation = Invocation.Create(func, name, Priority.High);
            foreach (var item in _recordings)
                item.UpdateRepeatCount(invocation);
            _recordings.Add(invocation);

            return invocation;
        }

        public static Invocation<T, object> CallTo<T>(Expression<Action<T>> action) where T : class
        {
            return CallTo(null, action);
        }

        public static Invocation<T, object> CallTo<T>(String name, Expression<Action<T>> action) where T : class
        {
            if (!Scenario.IsRecording())
                throw new InvalidContextException("A statements can only be used in an external scenario");

            Invocation<T, object> invocation = Invocation.Create(action, name, Priority.Medium);
            foreach (var item in _recordings)
                item.UpdateRepeatCount(invocation);
            _recordings.Add(invocation);
            return invocation;
        }
        
        internal static Invocation<T, object> PriorityCallTo<T>(T mock, Expression<Action<T>> action, Priority priority) where T : class
        {
            if (!Scenario.IsRecording())
                throw new InvalidContextException("A statements can only be used in an external scenario");

            Invocation<T, object> invocation = Invocation.Create(action, priority, mock);
            foreach (var item in _recordings)
                item.UpdateRepeatCount(invocation);
            _recordings.Add(invocation);
            return invocation;
        }

        public static void Fake<T>(T fakeInstance) where T : class
        {
            if (!Scenario.IsRecording())
                throw new InvalidContextException("A statements can only be used in an external scenario");

            if (fakeInstance == null)
                throw new ArgumentNullException("fakeInstance");

            foreach(var existingInteraction in DynamicDeclarations.GetDynamicInteractions())
            {
                if(existingInteraction.GetType() == typeof(UseFakeInteraction) && 
                    existingInteraction.RegistrationType == typeof(T))
                {
                    throw new InvalidContextException("Fake for type " + typeof(T).Name + " has already been defined in scenario");
                }
            }

            DynamicDeclarations.AddContainerInteraction(new UseFakeInteraction(typeof(T), fakeInstance));
        }

        public static void FakeLateBound<T>(string fieldName, T fakeInstance)
        {
            if (string.IsNullOrEmpty(fieldName))
                throw new ArgumentNullException("fieldName");

            if (!Scenario.IsRecording())
                throw new InvalidContextException("A statements can only be used in an external scenario");

            foreach (var existingInteraction in DynamicDeclarations.GetDynamicInteractions())
            {
                if (existingInteraction.GetType() == typeof(UseFakeLateBoundInteraction) &&
                    existingInteraction.RegistrationName == fieldName)
                {
                    throw new InvalidContextException("Fake for field " + fieldName + " has already been defined in scenario");
                }
            }

            DynamicDeclarations.AddContainerInteraction(new UseFakeLateBoundInteraction(fieldName, fakeInstance));
        }
    }
}
